<?php
    // +-----------------------------------------------------------------------+
    // | Copyright (c) 2002, Richard Heyes                                     |
    // | All rights reserved.                                                  |
    // |                                                                       |
    // | Redistribution and use in source and binary forms, with or without    |
    // | modification, are permitted provided that the following conditions    |
    // | are met:                                                              |
    // |                                                                       |
    // | o Redistributions of source code must retain the above copyright      |
    // |   notice, this list of conditions and the following disclaimer.       |
    // | o Redistributions in binary form must reproduce the above copyright   |
    // |   notice, this list of conditions and the following disclaimer in the |
    // |   documentation and/or other materials provided with the distribution.|
    // | o The names of the authors may not be used to endorse or promote      |
    // |   products derived from this software without specific prior written  |
    // |   permission.                                                         |
    // |                                                                       |
    // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
    // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
    // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
    // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
    // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
    // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
    // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
    // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
    // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
    // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
    // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
    // |                                                                       |
    // +-----------------------------------------------------------------------+
    // | Author: Richard Heyes <richard@phpguru.org>                           |
    // | Co-Author: Damian Fernandez Sosa <damlists@cnba.uba.ar>               |
    // +-----------------------------------------------------------------------+
    //
    // $Id: POP3.php 295243 2010-02-18 22:05:20Z clockwerx $
    /**
    * +----------------------------- IMPORTANT ------------------------------+
    * | Usage of this class compared to native php extensions such as IMAP   |
    * | is slow and may be feature deficient. If available you are STRONGLY  |
    * | recommended to use the php extensions.                               |
    * +----------------------------------------------------------------------+
    *
    * POP3 Access Class
    *
    * For usage see the example script
    */

    define ( 'NET_POP3_STATE_DISCONNECTED', 1, true );
    define ( 'NET_POP3_STATE_AUTHORISATION', 2, true );
    define ( 'NET_POP3_STATE_TRANSACTION', 4, true );

    class Net_POP3 {

        /**
        * Some basic information about the mail drop
        * garnered from the STAT command
        *
        * @var array
        */
        var $_maildrop;

        /**
        * Used for APOP to store the timestamp
        *
        * @var string
        */
        var $_timestamp;

        /**
        * Timeout that is passed to the socket object
        *
        * @var integer
        */
        var $_timeout;

        /**
        * Socket object
        *
        * @var object
        */
        var $_socket;

        /**
        * Current state of the connection. Used with the
        * constants defined above.
        *
        * @var integer
        */
        var $_state;

        /**
        * Hostname to connect to
        *
        * @var string
        */
        var $_host;

        /**
        * Port to connect to
        *
        * @var integer
        */
        var $_port;

        /**
        * To allow class debuging
        * @var boolean
        */
        var $_debug = false;

        /**
        * The auth methods this class support
        * @var array
        */
        //var $supportedAuthMethods=array('DIGEST-MD5', 'CRAM-MD5', 'APOP' , 'PLAIN' , 'LOGIN', 'USER');
        //Disabling DIGEST-MD5 for now
        var $supportedAuthMethods = array ('CRAM-MD5', 'APOP', 'PLAIN', 'LOGIN', 'USER' );
        //var $supportedAuthMethods=array( 'CRAM-MD5', 'PLAIN' , 'LOGIN');
        //var $supportedAuthMethods=array( 'PLAIN' , 'LOGIN');


        /**
        * The auth methods this class support
        * @var array
        */
        var $supportedSASLAuthMethods = array ('DIGEST-MD5', 'CRAM-MD5' );

        /**
        * The capability response
        * @var array
        */
        var $_capability;


        /**
        * Constructor. Sets up the object variables, and instantiates
        * the socket object.
        *
        */
        function Net_POP3($user, $pass, $host, $port = 110 ,$code = 'UTF-8') {
            $this->_timestamp = ''; // Used for APOP
            $this->_maildrop = array ();
            $this->_timeout = 3;
            $this->_state = NET_POP3_STATE_DISCONNECTED;
            $this->_socket = new Net_Socket ();
            $this->code = $code;
            /*
            * Include the Auth_SASL package.  If the package is not available,
            * we disable the authentication methods that depend upon it.
            */
            @include_once 'Auth/SASL.php';
            if (! class_exists ( 'Auth_SASL' )) {
                if ($this->_debug) {
                    echo "AUTH_SASL NOT PRESENT!\n";
                }
                foreach ( $this->supportedSASLAuthMethods as $SASLMethod ) {
                    $pos = array_search ( $SASLMethod, $this->supportedAuthMethods );
                    if ($this->_debug) {
                        echo "DISABLING METHOD $SASLMethod\n";
                    }
                    unset ( $this->supportedAuthMethods [$pos] );
                }
            }
            $this->parser = new mime_decode();
            $this->llogin($user, $pass, $host, $port);
        }
        /**
        * 整合登录方法
        * @access public
        * @return PEAR_Error
        */
        public function llogin($user, $pass, $host, $port = 110) {
            if (PEAR::isError ( $ret = $this->connect ( $host, $port ) )) {
                //echo "ERROR: " . $ret->getMessage () . "\n";
                echo json_encode(array('success'=>false, 'msg'=>'连接失败！'));
                exit ();
            }
            if (PEAR::isError ( $ret = $this->login ( $user, $pass, 'USER' ) )) {
                //echo "ERROR: " . $ret->getMessage () . "\n";
                echo json_encode(array('success'=>false, 'msg'=>'密码错误！!'));
                exit ();
            }

        }

        /**
        * 新的邮件列表
        * @access public
        * @return PEAR_Error
        */
        public function newList($top) {
            $list_rs = array();
            if($top > 100 ){
                $top = 100;
            }
            $stat = $this->_cmdStat();
            $newnum = $stat[0]; //最新邮件
            while($top){
                $list = $this->getListing($newnum);
                $h = $this->getParsedHeaders($newnum);
                
                preg_match('/\?(.+?)\?/i' , $h['Subject'] ,$matches);
                $scode = empty($matches[1]) ? $this->code : $matches[1];
                $subject = $this->decode_mime_string($h['Subject']);
                if(strtolower($scode) === 'gb18030') $scode = 'gbk';
                if(strtoupper($scode) != strtoupper($this->code)){
                    $subject = mb_convert_encoding($subject , $this->code , $scode);
                }
                $list['Subject'] = $subject;
                $list['From'] = $this->decode_mime_string($h['From']);
                if(strtoupper($scode) != strtoupper($this->code)){
                    $list['From'] = mb_convert_encoding($list['From'] , $this->code , $scode);
                }
                $list['Date'] = date( 'Y-m-d H:i:s' , strtotime( $h['Date']) );
                
                $list_rs[] = $list;
                $top -- ;
                $newnum --;
            }
            return $list_rs;
        }

        public function saveAttachment($mgs_id,$logpath,$eml, $user){
            $savePath = array();
            $list = $this->getListing($mgs_id);
            $uid = $list['uidl'];
            //$logpath_dir = $logpath . $uid;
            /*qinqf 2013 07 28 */
            $logpath_dir = $logpath . $user . '/' . $mgs_id;
            if(!is_dir($logpath_dir)){
                mkdir($logpath_dir , 0777, true); 
            }
            $mailmsg = $this->getMsg($mgs_id);
            $head = $this->getRawHeaders($mgs_id);
            $body = $this->getBody($mgs_id);
            $mailinfo = $this ->parser->get_mail_info($head);
            $boundary = $this ->parser ->get_boundary($mailinfo['content-type']);
            if($eml){
                file_put_contents($logpath_dir . '/message.eml', $mailmsg);
            }

            $mailStruct = $this->parser -> split_parts($boundary, $body);
            $savePath = $this->_save($mailStruct,$logpath_dir);
            return $savePath;
        }
        function _save($mailStruct,$logpath_dir){
            $savePath = array();
            if(empty($mailStruct) || empty($logpath_dir)){
                return $savePath;
            }

            foreach($mailStruct as $k => $v){
                $pattern_att = '/Content-Disposition: attachment;/';
                if(preg_match($pattern_att,$v)){
                    preg_match_all('/filename="(.+?)"/',$v,$matches,PREG_SET_ORDER    );
                    $filename = $matches[0][1];
                    //$filename = $this->decode_mime_string($filename);
                    list($m_h,$m_c) = explode("\r\n\r\n" , $v);
                    file_put_contents($logpath_dir.'/'.$filename , base64_decode($m_c) );
                    $savePath['file'][] = $filename; //$logpath_dir.'/'.$filename;
                } 
            }
            return $savePath;
        }


        /**
        * Handles the errors the class can find
        * on the server
        *
        * @access private
        * @return PEAR_Error
        */
        function _raiseError($msg, $code = -1) {
            include_once 'PEAR.php';
            return PEAR::raiseError ( $msg, $code );
        }

        /**
        * Connects to the given host on the given port.
        * Also looks for the timestamp in the greeting
        * needed for APOP authentication
        *
        * @param  string $host Hostname/IP address to connect to
        * @param  string $port Port to use to connect to on host
        * @return bool  Success/Failure
        */
        function connect($host = 'localhost', $port = 110) {
            $this->_host = $host;
            $this->_port = $port;

            $result = $this->_socket->connect ( $host, $port, false, $this->_timeout );
            if ($result === true) {
                $data = $this->_recvLn ();

                if ($this->_checkResponse ( $data )) {
                    // if the response begins with '+OK' ...
                    //            if (@substr(strtoupper($data), 0, 3) == '+OK') {
                    // Check for string matching apop timestamp
                    if (preg_match ( '/<.+@.+>/U', $data, $matches )) {
                        $this->_timestamp = $matches [0];
                    }
                    $this->_maildrop = array ();
                    $this->_state = NET_POP3_STATE_AUTHORISATION;

                    return true;
                }
            }

            $this->_socket->disconnect ();
            return false;
        }

        /**
        * Disconnect function. Sends the QUIT command
        * and closes the socket.
        *
        * @return bool Success/Failure
        */
        function disconnect() {
            return $this->_cmdQuit ();
        }

        /**
        * Performs the login procedure. If there is a timestamp
        * stored, APOP will be tried first, then basic USER/PASS.
        *
        * @param  string $user Username to use
        * @param  string $pass Password to use
        * @param  mixed $apop Whether to try APOP first, if used as string you can select the auth methd to use ( $pop3->login('validlogin', 'validpass', "CRAM-MD5");
        * Valid methods are: 'DIGEST-MD5','CRAM-MD5','LOGIN','PLAIN','APOP','USER'
        * @return mixed  true on Success/ PEAR_ERROR on error
        */
        function login($user, $pass, $apop = true) {
            if ($this->_state == NET_POP3_STATE_AUTHORISATION) {

                if (PEAR::isError ( $ret = $this->_cmdAuthenticate ( $user, $pass, $apop ) )) {
                    return $ret;
                }
                if (! PEAR::isError ( $ret )) {
                    $this->_state = NET_POP3_STATE_TRANSACTION;
                    return true;
                }

            }
            return $this->_raiseError ( 'Generic login error', 1 );
        }

        /**
        * Parses the response from the capability command. Stores
        * the result in $this->_capability
        *
        * @access private
        */
        function _parseCapability() {

            if (! PEAR::isError ( $data = $this->_sendCmd ( 'CAPA' ) )) {
                $data = $this->_getMultiline ();
            } else {
                // CAPA command not supported, reset data var
                //  to avoid Notice errors of preg_split on an object
                $data = '';
            }
            $data = preg_split ( '/\r?\n/', $data, - 1, PREG_SPLIT_NO_EMPTY );

            for($i = 0; $i < count ( $data ); $i ++) {

                $capa = '';
                if (preg_match ( '/^([a-z,\-]+)( ((.*))|$)$/i', $data [$i], $matches )) {

                    $capa = strtolower ( $matches [1] );
                    switch ($capa) {
                        case 'implementation' :
                            $this->_capability ['implementation'] = $matches [3];
                            break;
                        case 'sasl' :
                            $this->_capability ['sasl'] = preg_split ( '/\s+/', $matches [3] );
                            break;
                        default :
                            $this->_capability [$capa] = $matches [2];
                            break;
                    }
                }
            }
        }

        /**
        * Returns the name of the best authentication method that the server
        * has advertised.
        *
        * @param string if !=null,authenticate with this method ($userMethod).
        *
        * @return mixed    Returns a string containing the name of the best
        * supported authentication method or a PEAR_Error object
        * if a failure condition is encountered.
        * @access private
        * @since  1.0
        */
        function _getBestAuthMethod($userMethod = null) {
            /*
            return 'USER';
            return 'APOP';
            return 'DIGEST-MD5';
            return 'CRAM-MD5';
            */

            $this->_parseCapability ();

            //unset($this->_capability['sasl']);


            if (isset ( $this->_capability ['sasl'] )) {
                $serverMethods = $this->_capability ['sasl'];
            } else {
                $serverMethods = array ('USER' );
                // Check for timestamp before attempting APOP
                if ($this->_timestamp != null) {
                    $serverMethods [] = 'APOP';
                }
            }

            if ($userMethod !== null && $userMethod !== true) {
                $methods = array ();
                $methods [] = $userMethod;
                return $userMethod;
            } else {
                $methods = $this->supportedAuthMethods;
            }

            if (($methods != null) && ($serverMethods != null)) {

                foreach ( $methods as $method ) {

                    if (in_array ( $method, $serverMethods )) {
                        return $method;
                    }
                }
                $serverMethods = implode ( ',', $serverMethods );
                $myMethods = implode ( ',', $this->supportedAuthMethods );
                return $this->_raiseError ( "$method NOT supported authentication method!. This server " . "supports these methods: $serverMethods, but I support $myMethods" );
            } else {
                return $this->_raiseError ( "This server don't support any Auth methods" );
            }
        }

        /**
        * Handles the authentication using any known method
        *
        * @param string The userid to authenticate as.
        * @param string The password to authenticate with.
        * @param string The method to use ( if $usermethod == '' then the class chooses the best method (the stronger is the best ) )
        *
        * @return mixed  string or PEAR_Error
        * @access private
        * @since  1.0
        */
        function _cmdAuthenticate($uid, $pwd, $userMethod = null) {

            if (PEAR::isError ( $method = $this->_getBestAuthMethod ( $userMethod ) )) {
                return $method;
            }

            if (! in_array ( $userMethod, $this->supportedAuthMethods )) {
                return $this->_raiseError ( 'Authentication method "' . $userMethod . '" is not supported.' );
            }

            switch ($method) {
                case 'DIGEST-MD5' :
                    $result = $this->_authDigest_MD5 ( $uid, $pwd );
                    break;
                case 'CRAM-MD5' :
                    $result = $this->_authCRAM_MD5 ( $uid, $pwd );
                    break;
                case 'LOGIN' :
                    $result = $this->_authLOGIN ( $uid, $pwd );
                    break;
                case 'PLAIN' :
                    $result = $this->_authPLAIN ( $uid, $pwd );
                    break;
                case 'APOP' :
                    $result = $this->_cmdApop ( $uid, $pwd );
                    // if APOP fails fallback to USER auth
                    if (PEAR::isError ( $result )) {
                        //echo "APOP FAILED!!!\n";
                        $result = $this->_authUSER ( $uid, $pwd );
                    }
                    break;
                case 'USER' :
                    $result = $this->_authUSER ( $uid, $pwd );
                    break;

                default :
                    $result = $this->_raiseError ( "$method is not a supported authentication method" );
                    break;
            }
            return $result;
        }

        /**
        * Authenticates the user using the USER-PASS method.
        *
        * @param string The userid to authenticate as.
        * @param string The password to authenticate with.
        *
        * @return mixed    true on success or PEAR_Error on failure
        *
        * @access private
        * @since  1.0
        */
        function _authUSER($user, $pass) {
            if (PEAR::isError ( $ret = $this->_cmdUser ( $user ) )) {
                return $ret;
            }
            if (PEAR::isError ( $ret = $this->_cmdPass ( $pass ) )) {
                return $ret;
            }
            return true;
        }

        /**
        * Authenticates the user using the PLAIN method.
        *
        * @param string The userid to authenticate as.
        * @param string The password to authenticate with.
        *
        * @return array Returns an array containing the response
        *
        * @access private
        * @since  1.0
        */
        function _authPLAIN($user, $pass) {
            $cmd = sprintf ( 'AUTH PLAIN %s', base64_encode ( chr ( 0 ) . $user . chr ( 0 ) . $pass ) );

            if (PEAR::isError ( $ret = $this->_send ( $cmd ) )) {
                return $ret;
            }
            if (PEAR::isError ( $challenge = $this->_recvLn () )) {
                return $challenge;
            }
            if (PEAR::isError ( $ret = $this->_checkResponse ( $challenge ) )) {
                return $ret;
            }

            return true;
        }

        /**
        * Authenticates the user using the PLAIN method.
        *
        * @param string The userid to authenticate as.
        * @param string The password to authenticate with.
        *
        * @return array Returns an array containing the response
        *
        * @access private
        * @since  1.0
        */
        function _authLOGIN($user, $pass) {
            $this->_send ( 'AUTH LOGIN' );

            if (PEAR::isError ( $challenge = $this->_recvLn () )) {
                return $challenge;
            }
            if (PEAR::isError ( $ret = $this->_checkResponse ( $challenge ) )) {
                return $ret;
            }

            if (PEAR::isError ( $ret = $this->_send ( sprintf ( '%s', base64_encode ( $user ) ) ) )) {
                return $ret;
            }

            if (PEAR::isError ( $challenge = $this->_recvLn () )) {
                return $challenge;
            }
            if (PEAR::isError ( $ret = $this->_checkResponse ( $challenge ) )) {
                return $ret;
            }

            if (PEAR::isError ( $ret = $this->_send ( sprintf ( '%s', base64_encode ( $pass ) ) ) )) {
                return $ret;
            }

            if (PEAR::isError ( $challenge = $this->_recvLn () )) {
                return $challenge;
            }
            return $this->_checkResponse ( $challenge );
        }

        /**
        * Authenticates the user using the CRAM-MD5 method.
        *
        * @param string The userid to authenticate as.
        * @param string The password to authenticate with.
        *
        * @return array Returns an array containing the response
        *
        * @access private
        * @since  1.0
        */
        function _authCRAM_MD5($uid, $pwd) {
            if (PEAR::isError ( $ret = $this->_send ( 'AUTH CRAM-MD5' ) )) {
                return $ret;
            }

            if (PEAR::isError ( $challenge = $this->_recvLn () )) {
                return $challenge;
            }
            if (PEAR::isError ( $ret = $this->_checkResponse ( $challenge ) )) {
                return $ret;
            }

            // remove '+ '


            $challenge = substr ( $challenge, 2 );

            $challenge = base64_decode ( $challenge );

            $cram = &Auth_SASL::factory ( 'crammd5' );
            $auth_str = base64_encode ( $cram->getResponse ( $uid, $pwd, $challenge ) );

            if (PEAR::isError ( $error = $this->_send ( $auth_str ) )) {
                return $error;
            }
            if (PEAR::isError ( $ret = $this->_recvLn () )) {
                return $ret;
            }
            //echo "RET:$ret\n";
            return $this->_checkResponse ( $ret );
        }

        /**
        * Authenticates the user using the DIGEST-MD5 method.
        *
        * @param string The userid to authenticate as.
        * @param string The password to authenticate with.
        * @param string The efective user
        *
        * @return array Returns an array containing the response
        *
        * @access private
        * @since  1.0
        */
        function _authDigest_MD5($uid, $pwd) {
            if (PEAR::isError ( $ret = $this->_send ( 'AUTH DIGEST-MD5' ) )) {
                return $ret;
            }

            if (PEAR::isError ( $challenge = $this->_recvLn () )) {
                return $challenge;
            }
            if (PEAR::isError ( $ret = $this->_checkResponse ( $challenge ) )) {
                return $ret;
            }

            // remove '+ '
            $challenge = substr ( $challenge, 2 );

            $challenge = base64_decode ( $challenge );
            $digest = &Auth_SASL::factory ( 'digestmd5' );
            $auth_str = base64_encode ( $digest->getResponse ( $uid, $pwd, $challenge, "localhost", "pop3" ) );

            if (PEAR::isError ( $error = $this->_send ( $auth_str ) )) {
                return $error;
            }

            if (PEAR::isError ( $challenge = $this->_recvLn () )) {
                return $challenge;
            }
            if (PEAR::isError ( $ret = $this->_checkResponse ( $challenge ) )) {
                return $ret;
            }
            /*
            * We don't use the protocol's third step because POP3 doesn't allow
            * subsequent authentication, so we just silently ignore it.
            */

            if (PEAR::isError ( $challenge = $this->_send ( "\r\n" ) )) {
                return $challenge;
            }

            if (PEAR::isError ( $challenge = $this->_recvLn () )) {
                return $challenge;
            }

            return $this->_checkResponse ( $challenge );
        }

        /**
        * Sends the APOP command
        *
        * @param  $user Username to send
        * @param  $pass Password to send
        * @return bool Success/Failure
        */
        function _cmdApop($user, $pass) {
            if ($this->_state == NET_POP3_STATE_AUTHORISATION) {

                if (! empty ( $this->_timestamp )) {
                    if (PEAR::isError ( $data = $this->_sendCmd ( 'APOP ' . $user . ' ' . md5 ( $this->_timestamp . $pass ) ) )) {
                        return $data;
                    }
                    $this->_state = NET_POP3_STATE_TRANSACTION;
                    return true;
                }
            }
            return $this->_raiseError ( 'Not In NET_POP3_STATE_AUTHORISATION State1' );
        }

        /**
        * Returns the raw headers of the specified message.
        *
        * @param  integer $msg_id Message number
        * @return mixed   Either raw headers or false on error
        */
        function getRawHeaders($msg_id) {
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {
                return $this->_cmdTop ( $msg_id, 0 );
            }

            return false;
        }

        /**
        * Returns the  headers of the specified message in an
        * associative array. Array keys are the header names, array
        * values are the header values. In the case of multiple headers
        * having the same names, eg Received:, the array value will be
        * an indexed array of all the header values.
        *
        * @param  integer $msg_id Message number
        * @return mixed   Either array of headers or false on error
        */
        function getParsedHeaders($msg_id) {
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {

                $raw_headers = rtrim ( $this->getRawHeaders ( $msg_id ) );

                $raw_headers = preg_replace ( "/\r\n[ \t]+/", ' ', $raw_headers ); // Unfold headers
                $raw_headers = explode ( "\r\n", $raw_headers );
                foreach ( $raw_headers as $value ) {
                    $name = substr ( $value, 0, $pos = strpos ( $value, ':' ) );
                    $value = ltrim ( substr ( $value, $pos + 1 ) );
                    if (isset ( $headers [$name] ) and is_array ( $headers [$name] )) {
                        $headers [$name] [] = $value;
                    } elseif (isset ( $headers [$name] )) {
                        $headers [$name] = array ($headers [$name], $value );
                    } else {
                        $headers [$name] = $value;
                    }
                }

                return $headers;
            }

            return false;
        }

        /**
        * Returns the body of the message with given message number.
        *
        * @param  integer $msg_id Message number
        * @return mixed   Either message body or false on error
        */
        function getBody($msg_id) {
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {
                $msg = $this->_cmdRetr ( $msg_id );
                return substr ( $msg, strpos ( $msg, "\r\n\r\n" ) + 4 );//var_dump($msg, substr ( $msg, strpos ( $msg, "\r\n\r\n" ) + 4 ));die;
            }

            return false;
        }

        /**
        * Returns the entire message with given message number.
        *
        * @param  integer $msg_id Message number
        * @return mixed   Either entire message or false on error
        */
        function getMsg($msg_id) {
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {
                return $this->_cmdRetr ( $msg_id );
            }

            return false;
        }

        /**
        * Returns the size of the maildrop
        *
        * @return mixed Either size of maildrop or false on error
        */
        function getSize() {
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {
                if (isset ( $this->_maildrop ['size'] )) {
                    return $this->_maildrop ['size'];
                } else {
                    list ( , $size ) = $this->_cmdStat ();
                    return $size;
                }
            }

            return false;
        }

        /**
        * Returns number of messages in this maildrop
        *
        * @return mixed Either number of messages or false on error
        */
        function numMsg() {
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {
                if (isset ( $this->_maildrop ['num_msg'] )) {
                    return $this->_maildrop ['num_msg'];
                } else {
                    list ( $num_msg, ) = $this->_cmdStat ();
                    return $num_msg;
                }
            }

            return false;
        }

        /**
        * Marks a message for deletion. Only will be deleted if the
        * disconnect() method is called.
        *
        * @param  integer $msg_id Message to delete
        * @return bool Success/Failure
        */
        function deleteMsg($msg_id) {
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {
                return $this->_cmdDele ( $msg_id );
            }

            return false;
        }

        /**
        * Combination of LIST/UIDL commands, returns an array
        * of data
        *
        * @param  integer $msg_id Optional message number
        * @return mixed Array of data or false on error
        */
        function getListing($msg_id = null) {

            if ($this->_state == NET_POP3_STATE_TRANSACTION) {
                if (! isset ( $msg_id )) {

                    $list = array ();
                    if ($list = $this->_cmdList ()) {
                        if ($uidl = $this->_cmdUidl ()) {
                            foreach ( $uidl as $i => $value ) {
                                $list [$i] ['uidl'] = $value ['uidl'];
                            }
                        }
                        return $list;
                    } else {
                        return array ();
                    }
                } else {
                    if ($list = $this->_cmdList ( $msg_id ) and $uidl = $this->_cmdUidl ( $msg_id )) {
                        return array_merge ( $list, $uidl );
                    }
                }
            }

            return false;
        }

        /**
        * Sends the USER command
        *
        * @param  string $user Username to send
        * @return bool  Success/Failure
        */
        function _cmdUser($user) {
            if ($this->_state == NET_POP3_STATE_AUTHORISATION) {
                return $this->_sendCmd ( 'USER ' . $user );
            }
            return $this->_raiseError ( 'Not In NET_POP3_STATE_AUTHORISATION State' );
        }

        /**
        * Sends the PASS command
        *
        * @param  string $pass Password to send
        * @return bool  Success/Failure
        */
        function _cmdPass($pass) {
            if ($this->_state == NET_POP3_STATE_AUTHORISATION) {
                return $this->_sendCmd ( 'PASS ' . $pass );
            }
            return $this->_raiseError ( 'Not In NET_POP3_STATE_AUTHORISATION State' );
        }

        /**
        * Sends the STAT command
        *
        * @return mixed Indexed array of number of messages and
        * maildrop size, or false on error.
        */
        function _cmdStat() {
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {
                if (! PEAR::isError ( $data = $this->_sendCmd ( 'STAT' ) )) {
                    sscanf ( $data, '+OK %d %d', $msg_num, $size );
                    $this->_maildrop ['num_msg'] = $msg_num;
                    $this->_maildrop ['size'] = $size;

                    return array ($msg_num, $size );
                }
            }
            return false;
        }

        /**
        * Sends the LIST command
        *
        * @param  integer $msg_id Optional message number
        * @return mixed   Indexed array of msg_id/msg size or
        * false on error
        */
        function _cmdList($msg_id = null) {
            $return = array ();
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {
                if (! isset ( $msg_id )) {
                    if (! PEAR::isError ( $data = $this->_sendCmd ( 'LIST' ) )) {
                        $data = $this->_getMultiline ();
                        $data = explode ( "\r\n", $data );
                        foreach ( $data as $line ) {
                            if ($line != '') {
                                sscanf ( $line, '%s %s', $msg_id, $size );
                                $return [] = array ('msg_id' => $msg_id, 'size' => $size );
                            }
                        }
                        return $return;
                    }
                } else {
                    if (! PEAR::isError ( $data = $this->_sendCmd ( 'LIST ' . $msg_id ) )) {
                        if ($data != '') {
                            sscanf ( $data, '+OK %d %d', $msg_id, $size );
                            return array ('msg_id' => $msg_id, 'size' => $size );
                        }
                        return array ();
                    }
                }
            }

            return false;
        }

        /**
        * Sends the RETR command
        *
        * @param  integer $msg_id The message number to retrieve
        * @return mixed   The message or false on error
        */
        function _cmdRetr($msg_id) {
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {
                if (! PEAR::isError ( $data = $this->_sendCmd ( 'RETR ' . $msg_id ) )) {
                    $data = $this->_getMultiline ();
                    return $data;
                }
            }

            return false;
        }

        /**
        * Sends the DELE command
        *
        * @param  integer $msg_id Message number to mark as deleted
        * @return bool Success/Failure
        */
        function _cmdDele($msg_id) {
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {
                return $this->_sendCmd ( 'DELE ' . $msg_id );
            }

            return false;
        }

        /**
        * Sends the NOOP command
        *
        * @return bool Success/Failure
        */
        function _cmdNoop() {
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {
                if (! PEAR::isError ( $data = $this->_sendCmd ( 'NOOP' ) )) {
                    return true;
                }
            }

            return false;
        }

        /**
        * Sends the RSET command
        *
        * @return bool Success/Failure
        */
        function _cmdRset() {
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {
                if (! PEAR::isError ( $data = $this->_sendCmd ( 'RSET' ) )) {
                    return true;
                }
            }

            return false;
        }

        /**
        * Sends the QUIT command
        *
        * @return bool Success/Failure
        */
        function _cmdQuit() {
            $data = $this->_sendCmd ( 'QUIT' );
            $this->_state = NET_POP3_STATE_DISCONNECTED;
            $this->_socket->disconnect ();

            return ( bool ) $data;
        }

        /**
        * Sends the TOP command
        *
        * @param  integer  $msg_id    Message number
        * @param  integer  $num_lines Number of lines to retrieve
        * @return mixed Message data or false on error
        */
        function _cmdTop($msg_id, $num_lines) {
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {

                if (! PEAR::isError ( $data = $this->_sendCmd ( 'TOP ' . $msg_id . ' ' . $num_lines ) )) {
                    return $this->_getMultiline ();
                }
            }

            return false;
        }

        /**
        * Sends the UIDL command
        *
        * @param  integer $msg_id Message number
        * @return mixed indexed array of msg_id/uidl or false on error
        */
        function _cmdUidl($msg_id = null) {
            if ($this->_state == NET_POP3_STATE_TRANSACTION) {

                if (! isset ( $msg_id )) {
                    if (! PEAR::isError ( $data = $this->_sendCmd ( 'UIDL' ) )) {
                        $data = $this->_getMultiline ();
                        $data = explode ( "\r\n", $data );
                        foreach ( $data as $line ) {
                            sscanf ( $line, '%d %s', $msg_id, $uidl );
                            $return [] = array ('msg_id' => $msg_id, 'uidl' => $uidl );
                        }

                        return $return;
                    }
                } else {

                    $data = $this->_sendCmd ( 'UIDL ' . $msg_id );
                    sscanf ( $data, '+OK %d %s', $msg_id, $uidl );
                    return array ('msg_id' => $msg_id, 'uidl' => $uidl );
                }
            }

            return false;
        }

        /**
        * Sends a command, checks the reponse, and
        * if good returns the reponse, other wise
        * returns false.
        *
        * @param  string $cmd  Command to send (\r\n will be appended)
        * @return mixed First line of response if successful, otherwise false
        */
        function _sendCmd($cmd) {
            if (PEAR::isError ( $result = $this->_send ( $cmd ) )) {
                return $result;
            }

            if (PEAR::isError ( $data = $this->_recvLn () )) {
                return $data;
            }

            if (strtoupper ( substr ( $data, 0, 3 ) ) == '+OK') {
                return $data;
            }

            return $this->_raiseError ( $data );
        }

        /**
        * Reads a multiline reponse and returns the data
        *
        * @return string The reponse.
        */
        function _getMultiline() {
            $data = '';
            while ( ! PEAR::isError ( $tmp = $this->_recvLn () ) ) {
                if ($tmp == '.' || $tmp == "\n.") {
                    return substr ( $data, 0, - 2 );
                }
                if (substr ( $tmp, 0, 2 ) == '..') {
                    $tmp = substr ( $tmp, 1 );
                }
                $data .= $tmp . "\r\n";
            }
            return substr ( $data, 0, - 2 );
        }

        /**
        * Sets the bebug state
        *
        * @param  bool $debug
        * @access public
        * @return void
        */
        function setDebug($debug = true) {
            $this->_debug = $debug;
        }

        /**
        * Send the given string of data to the server.
        *
        * @param   string  $data       The string of data to send.
        *
        * @return  mixed   True on success or a PEAR_Error object on failure.
        *
        * @access  private
        * @since   1.0
        */
        function _send($data) {
            if ($this->_debug) {
                echo "C: $data\n";
            }

            if (PEAR::isError ( $error = $this->_socket->writeLine ( $data ) )) {
                return $this->_raiseError ( 'Failed to write to socket: ' . $error->getMessage () );
            }
            return true;
        }

        /**
        * Receive the given string of data from the server.
        *
        * @return  mixed   a line of response on success or a PEAR_Error object on failure.
        *
        * @access  private
        * @since  1.0
        */
        function _recvLn() {
            if (PEAR::isError ( $lastline = $this->_socket->readLine ( 8192 ) )) {
                return $this->_raiseError ( 'Failed to read from socket: ' . $this->lastline->getMessage () );
            }
            if ($this->_debug) {
                // S: means this data was sent by  the POP3 Server
                echo "S:$lastline\n";
            }
            return $lastline;
        }

        /**
        * Checks de server Response
        *
        * @param  string $response the response
        * @return  mixed   true on success or a PEAR_Error object on failure.
        *
        * @access  private
        * @since  1.3.3
        */
        function _checkResponse($response) {
            if (@substr ( strtoupper ( $response ), 0, 3 ) == '+OK') {
                return true;
            } else {
                if (@substr ( strtoupper ( $response ), 0, 4 ) == '-ERR') {
                    return $this->_raiseError ( $response );
                } else {
                    if (@substr ( strtoupper ( $response ), 0, 2 ) == '+ ') {
                        return true;
                    }
                }

            }
            return $this->_raiseError ( "Unknown Response ($response)" );
        }

        function decode_mime_string($string) {
            if(($pos = strpos($string,"=?")) === false) return $string;
            while(!($pos === false)) {
                $newresult .= substr($string,0,$pos);
                $string = substr($string,$pos+2,strlen($string));
                $intpos = strpos($string,"?");
                $charset = substr($string,0,$intpos);
                $enctype = strtolower(substr($string,$intpos+1,1));
                $string = substr($string,$intpos+3,strlen($string));
                $endpos = strpos($string,"?=");
                $mystring = substr($string,0,$endpos);
                $string = substr($string,$endpos+2,strlen($string));
                if($enctype == "q") {
                    $mystring = str_replace("_"," ",$mystring);
                    $mystring = $this->decode_qp($mystring);
                } else if ($enctype == "b")
                        $mystring = base64_decode($mystring);
                    $newresult .= $mystring;
                $pos = strpos($string,"=?");
            }
            return $newresult.$string;
        }

        function decode_qp($text) {
            $text = quoted_printable_decode($text);
            /*
            $text = str_replace("\r","",$text);
            $text = ereg_replace("=\n", "", $text);
            $text = str_replace("\n","\r\n",$text);
            */
            $text = ereg_replace("=\r", "\r", $text);
            return $text;
        }

    }

?>
